/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: cbus_service.c
 *
 * Purpose: implementation service side for cbus
 *
 * Developer:
 *   wen.gu , 2022-02-16
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/

#include "cbus/bus/cbus_service.h"

#include <string.h>

#include "cbus/bus/cbus_endpoint.h"
#include "cbus/bus/cbus_message.h"
#include "cbus_service_private.h"

#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static void cbus_service_endpoint_on_connect(GPTR opaque, cbus_endpoint_id endpoint_id, GBOL is_connected)
{
    endpoint_info_t* ep_info = (endpoint_info_t*)opaque;
    LOGD("[%s.%d]===>: receive connect request, ep id: %p, status:%s\n", __FUNCTION__, __LINE__, endpoint_id, is_connected ? "connected": "disconnected");
    //todo something
    if (is_connected == GFALSE)
    {/** remove subcribe infor */
        cbus_event_publisher_remove_subscriber(ep_info->service_ref->event_publisher, ep_info->service_ref, endpoint_id);
    }
}

static GErrc cbus_service_endpoint_create(cbus_service_t srv, const GSTR* url_array)
{
    const GSTR url = NULL;
    endpoint_info_t* endpoint_array = srv->endpoint_array;

    for (GU32 i = 0; url_array[i] != NULL; i++)
    {
        if (i >= CBUS_SERVICE_ENDPOINT_ARRAY_SIZE)
        {
            return G_ErrOutOfRange;
        }
 
        GErrc ret = cbus_endpoint_create(&endpoint_array[srv->endpoint_array_count].endpoint, CBUS_EP_ROLE_SERVICE, "default", url_array[i]);
        //LOGD("[%s.%d]===>: i: %d, ret(0x%08x)\n", __FUNCTION__, __LINE__, i, ret);
        if (G_OK != ret)
        {
            LOGE("create endpoint instance for url(%s)failed(0x%08x)\n", url_array[i], ret);
            return ret;
        }

        endpoint_array[srv->endpoint_array_count].service_ref = srv;
        srv->endpoint_array_count++;
    }

    return G_OK;    
}

static void cbus_service_endpoint_destroy(cbus_service_t srv)
{
    endpoint_info_t* endpoint_array = srv->endpoint_array;
    //LOGD("[%s.%d]===>:, info_conut: %d\n", __FUNCTION__, __LINE__, srv->endpoint_array_count);
    for (GU32 i = 0; i < srv->endpoint_array_count; i++)
    {
        cbus_endpoint_destroy(endpoint_array[i].endpoint);
       // cbus_map_destroy(endpoint_array[i].endpoint_id_map);
    }    
}

static GErrc cbus_service_endpoint_start(cbus_service_t srv)
{
    endpoint_info_t* endpoint_array = srv->endpoint_array;
    GBOL has_success_endpoint = GFALSE;

    for (GU32 i = 0; i < srv->endpoint_array_count; i++)
    {
        cbus_endpoint_t endpoint = (endpoint_array[i].endpoint);
        GErrc ret = cbus_endpoint_start(endpoint);
        if (G_OK == ret)
        {
            ret = cbus_endpoint_set_on_connect(endpoint, cbus_service_endpoint_on_connect, &endpoint_array[i]);

            if (G_OK != ret)
            {
                LOGE("set on connect for endpoint(%d) failed(0x%08x)\n", i, ret); /** todo, how to process this error case */
                //return ret;                
                continue;
            }

            has_success_endpoint = GTRUE;
        }
        else
        {
            LOGE("start endpoint(%d) failed(0x%08x)\n", i, ret); /** todo, how to process this error case */
            //return ret;
        }
    }   

    return (has_success_endpoint == GTRUE) ? G_OK : G_ErrUndefined;
}

static GErrc cbus_service_endpoint_stop(cbus_service_t srv)
{
    endpoint_info_t* endpoint_array = srv->endpoint_array;
    for (GU32 i = 0; i < srv->endpoint_array_count; i++)
    {
        GErrc ret = cbus_endpoint_stop(endpoint_array[i].endpoint);

        if (G_OK != ret)
        {
            LOGE("stop endpoint(%d) failed(0x%08x)\n", i, ret); /** todo, how to process this error case */
        }
    }   

    return G_OK;
}

static GErrc service_event_publisher_offer(cbus_service_t srv)
{
    GErrc ret = cbus_service_message_register_category(srv, CBUS_MSG_TYPE_Event_Subscribe, cbus_event_publisher_on_subscribe, srv->event_publisher);

    if (G_OK != ret)
    {
        LOGE("register subscribe handler faield(0x%08x)\n", ret);
        return ret;
    }

    ret = cbus_service_message_register_category(srv, CBUS_MSG_TYPE_Event_Stop_Subscribe, cbus_event_publisher_on_stop_subscribe, srv->event_publisher);

    if (G_OK != ret)
    {
        LOGE("register stop subscribe handler faield(0x%08x)\n", ret);
        return ret;
    }

    return G_OK;
}

static GErrc service_event_publisher_stop(cbus_service_t srv)
{
    GErrc ret = cbus_event_publisher_clear_subsciber(srv->event_publisher);

     if (G_OK != ret)
    {
        LOGE("clear all event subscriber faield(0x%08x)\n", ret);
        return ret;
    }   
    
    ret = cbus_service_message_unregister_category(srv, CBUS_MSG_TYPE_Event_Subscribe);

    if (G_OK != ret)
    {
        LOGE("unregister subscribe handler faield(0x%08x)\n", ret);
        return ret;
    }

    ret = cbus_service_message_unregister_category(srv, CBUS_MSG_TYPE_Event_Stop_Subscribe);

    if (G_OK != ret)
    {
        LOGE("unregister stop subscribe handler faield(0x%08x)\n", ret);
        return ret;
    }  

    return G_OK;
}
/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
/** use 'NULL' as the end of url_array */
GErrc cbus_service_create(cbus_service_t* srv, const GSTR* url_array)
{
    if (!srv || !url_array || (url_array[0] == NULL))
    {
        return G_ErrBadParameter;
    }

    cbus_service_t csrv = (cbus_service_t)malloc(sizeof(struct _cbus_service));

    if (!csrv)
    {
        LOGE("alloc service instance failed\n");
        return G_ErrInsufficientResources;
    }

    memset(csrv, 0, sizeof(struct _cbus_service));

    GErrc ret = cbus_service_endpoint_create(csrv, url_array);

    if (G_OK != ret)
    {
        cbus_service_destroy(csrv);
        return ret;
    }

    csrv->event_publisher = cbus_event_publisher_create();

    if (!csrv->event_publisher)
    {
        cbus_service_destroy(csrv);
        return G_ErrInsufficientResources;
    }

    csrv->properties = cbus_srv_property_create(csrv);

    if (!csrv->properties)
    {
        cbus_service_destroy(csrv);
        return G_ErrInsufficientResources;        
    }

    *srv = csrv;
    return G_OK;
}

void cbus_service_destroy(cbus_service_t srv)
{
    if (srv)
    {
        cbus_service_endpoint_destroy(srv);
        cbus_event_publisher_destroy(srv->event_publisher);
        cbus_srv_property_destroy(srv->properties);

        free(srv);
    }
}

GErrc cbus_service_start(cbus_service_t srv)
{
    if (!srv)
    {
        return G_ErrBadParameter;
    }

    GErrc ret = service_event_publisher_offer(srv);

    if (G_OK != ret)
    {
        LOGE("offer event module failed(0x%08x)\n", ret);
        return ret;
    }

    ret = cbus_srv_property_offer(srv->properties);

    if (G_OK != ret)
    {
        LOGE("offer property module failed(0x%08x)\n", ret);
        return ret;
    }

    return cbus_service_endpoint_start(srv);
}

GErrc cbus_service_stop(cbus_service_t srv)
{
    if (!srv)
    {
        return G_ErrBadParameter;
    }

    GErrc ret = service_event_publisher_stop(srv);

    if (G_OK != ret)
    {
        LOGE("offer event module failed(0x%08x)\n", ret);
        //return ret;
    }

    ret = cbus_srv_property_stop(srv->properties);

    if (G_OK != ret)
    {
        LOGE("offer property module failed(0x%08x)\n", ret);
        //return ret;
    }
    return cbus_service_endpoint_stop(srv);
}

#if 0
cbus_message_t cbus_service_alloc_message(cbus_service_t srv, cbus_message_type_t message_type, cbus_message_id message_id, GU32 payload_size)
{
    if (!srv)
    {
        return NULL;
    }

    //cbus_message_t message = cbus_message_alloc()
}
#endif

GErrc cbus_service_method_register(cbus_service_t srv, cbus_message_id method_id, cbus_service_method method, GPTR opaque)
{
    if (!srv || !method)
    {
        return G_ErrBadParameter;
    }   

    return cbus_service_message_register(srv, CBUS_MSG_TYPE_Method_Request, method_id, method, opaque);
}

GErrc cbus_service_method_send_response(cbus_service_t srv, cbus_message_t message)
{
    if (!srv || !message)
    {
        return G_ErrBadParameter;
    } 

    return cbus_message_send(message); 
}

GErrc cbus_service_method_send_response_error(cbus_service_t srv, cbus_message_t request_message, GU08 error_code)
{
    if (!srv || !request_message)
    {
        return G_ErrBadParameter;
    }

    return cbus_message_send_response_code(request_message, error_code);    
}

GErrc cbus_service_event_notify(cbus_service_t srv, cbus_message_t event_data)
{
    if (!srv || !event_data)
    {
        return G_ErrBadParameter;
    }

     if (!srv->event_publisher)
    {
        return G_ErrInvalidStatus;
    }   

    return cbus_event_publisher_notify(srv->event_publisher, event_data);
}

GErrc cbus_service_property_register(cbus_service_t srv, const cbus_service_property_item_t* property_info)
{
    if (!srv || !property_info)
    {
        return G_ErrBadParameter;
    }

    if (!srv->properties)
    {
        return G_ErrInvalidStatus;
    }

    return cbus_srv_property_register(srv->properties, property_info);
}

GErrc cbus_service_property_register_group(cbus_service_t srv, const cbus_service_property_item_t* group, GU32 group_size)
{
    if (!srv || !group || (group_size == 0))
    {
        return G_ErrBadParameter;
    }

    if (!srv->properties)
    {
        return G_ErrInvalidStatus;
    }

    return cbus_srv_property_register_group(srv->properties, group, group_size);
}

GErrc cbus_service_property_unregister(cbus_service_t srv, cbus_message_id property_id)
{
    if (!srv)
    {
        return G_ErrBadParameter;
    }

    if (!srv->properties)
    {
        return G_ErrInvalidStatus;
    }

    return cbus_srv_property_unregister(srv->properties, property_id);
}

GErrc cbus_service_property_unregister_all(cbus_service_t srv)
{
    if (!srv)
    {
        return G_ErrBadParameter;
    }

    if (!srv->properties)
    {
        return G_ErrInvalidStatus;
    }

    return cbus_srv_property_unregister_all(srv->properties);
}


GErrc cbus_service_property_update(cbus_service_t srv, cbus_message_t property_value)
{
    if (!srv || !property_value)
    {
        return G_ErrBadParameter;
    }

    if (!srv->properties)
    {
        return G_ErrInvalidStatus;
    }

    return cbus_srv_property_update_value(srv->properties, property_value);
}
